{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 信息抽取\n",
    "https://python.langchain.com/docs/use_cases/extraction/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "#导入语言模型\n",
    "import os\n",
    "from langchain_community.llms import Tongyi\n",
    "from langchain_community.llms import SparkLLM\n",
    "from langchain_community.llms import QianfanLLMEndpoint\n",
    "\n",
    "import pandas as pd\n",
    "#导入模版\n",
    "from langchain.prompts import PromptTemplate\n",
    "\n",
    "#导入聊天模型\n",
    "from langchain.prompts.chat import (\n",
    "    ChatPromptTemplate,\n",
    "    SystemMessagePromptTemplate,\n",
    "    AIMessagePromptTemplate,\n",
    "    HumanMessagePromptTemplate,\n",
    ")\n",
    "from langchain.schema import (\n",
    "    AIMessage,\n",
    "    HumanMessage,\n",
    "    SystemMessage\n",
    ")\n",
    "\n",
    "from langchain_community.chat_models import ChatSparkLLM\n",
    "from langchain_community.chat_models.tongyi import ChatTongyi\n",
    "from langchain_community.chat_models import QianfanChatEndpoint\n",
    "\n",
    "#输入三个模型各自的key\n",
    "\n",
    "os.environ[\"DASHSCOPE_API_KEY\"] = \"\"\n",
    "\n",
    "os.environ[\"IFLYTEK_SPARK_APP_ID\"] = \"\"\n",
    "os.environ[\"IFLYTEK_SPARK_API_KEY\"] = \"\"\n",
    "os.environ[\"IFLYTEK_SPARK_API_SECRET\"] = \"\"\n",
    "\n",
    "os.environ[\"QIANFAN_AK\"] = \"\"\n",
    "os.environ[\"QIANFAN_SK\"] = \"\"\n",
    "\n",
    "from operator import itemgetter\n",
    "from langchain_core.runnables import RunnableLambda, RunnablePassthrough\n",
    "from langchain_core.output_parsers import StrOutputParser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_ty = Tongyi(temperature=0)\n",
    "model_qf = QianfanLLMEndpoint(temperature=0)\n",
    "model_sp = ChatSparkLLM()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "from dashscope import Generation\n",
    "import dashscope\n",
    "from langchain.tools.render import render_text_description\n",
    " \n",
    "dashscope.api_key = \"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'0.1.9'"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import langchain\n",
    "langchain.__version__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "import networkx"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "#! pip install openpyxl -i https://mirrors.aliyun.com/pypi/simple"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [],
   "source": [
    "#! pip install matplotlib -i https://mirrors.aliyun.com/pypi/simple"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## langchain的信息抽取\n",
    "\n",
    "https://python.langchain.com/docs/use_cases/extraction/\n",
    "\n",
    "* 大模型信息抽取的三种方式\n",
    "\n",
    "1. 工具/函数调用模式：一些LLM支持工具或函数调用模式，可以根据给定的模式构建输出。一般来说，这种方法最容易使用，并且有望产生良好的结果。\n",
    "\n",
    "2. JSON 模式：一些 LLM 可以强制输出有效的 JSON。这与工具/函数调用方法类似，只不过输出模式是作为提示的一部分提供的。一般来说，我们的直觉是，这比工具/函数调用方法的性能更差，但不要相信我们并针对您自己的用例进行验证！\n",
    "\n",
    "https://blog.csdn.net/wshzd/article/details/135834436\n",
    "\n",
    "3. 基于提示：可以很好地遵循说明的LLM可以被指示生成所需格式的文本。可以使用现有的输出解析器或使用自定义解析器将生成的文本解析为结构化格式（例如 JSON）。此方法可用于不支持 JSON 模式或工具/函数调用模式的 LLM。这种方法具有更广泛的适用性，但可能会比针对提取或函数调用进行微调的模型产生更差的结果。\n",
    "\n",
    "* llm.with_structured_output(schema=Person)  方法的作用\n",
    "\n",
    "https://github.com/langchain-ai/langchain/blob/a4896da2a0264c3405b5a97d03fff673ddee8402/libs/partners/openai/langchain_openai/chat_models/base.py#L765"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 单个对象的抽取"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Optional\n",
    "\n",
    "from langchain_core.pydantic_v1 import BaseModel, Field\n",
    "\n",
    "\n",
    "class Person(BaseModel):\n",
    "    \"\"\"Information about a person.\"\"\"\n",
    "    name: Optional[str] = Field(default=None, description=\"The name of the person\")\n",
    "    hair_color: Optional[str] = Field(default=None, description=\"The color of the peron's hair if known\")\n",
    "    height_in_meters: Optional[str] = Field(default=None, description=\"Height measured in meters\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Optional\n",
    "\n",
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "from langchain_core.pydantic_v1 import BaseModel, Field\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"You are an expert extraction algorithm. \"\n",
    "            \"Only extract relevant information from the text. \"\n",
    "            \"If you do not know the value of an attribute asked to extract, \"\n",
    "            \"return null for the attribute's value.\",\n",
    "        ),\n",
    "        (\"human\", \"{text}\"),\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.utils.function_calling import convert_to_openai_tool"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'type': 'function',\n",
       " 'function': {'name': 'Person',\n",
       "  'description': 'Information about a person.',\n",
       "  'parameters': {'type': 'object',\n",
       "   'properties': {'name': {'description': 'The name of the person',\n",
       "     'type': 'string'},\n",
       "    'hair_color': {'description': \"The color of the peron's hair if known\",\n",
       "     'type': 'string'},\n",
       "    'height_in_meters': {'description': 'Height measured in meters',\n",
       "     'type': 'string'}}}}}"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "convert_to_openai_tool(Person)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person(BaseModel):\n",
    "    \"\"\"Information about a person.\"\"\"\n",
    "    name: Optional[str] = Field(default=None, description=\"The name of the person\")\n",
    "    hair_color: Optional[str] = Field(default=None, description=\"The color of the peron's hair if known\")\n",
    "    height_in_meters: Optional[str] = Field(default=None, description=\"Height measured in meters\")\n",
    "    \n",
    "def get_response_1t(mess):\n",
    "    response = Generation.call(\n",
    "        model='qwen-plus',\n",
    "        messages=mess,\n",
    "        tools=[convert_to_openai_tool(Person)],\n",
    "        result_format='message', # 将输出设置为message形式\n",
    "    )\n",
    "    return response\n",
    "\n",
    "def prompt_ty(input):\n",
    "    pro_sys=\"You are an expert extraction algorithm.Only extract relevant information from the text.If you do not know the value of an attribute asked to extract,return null for the attribute's value.\"\n",
    "    return [{\"role\":\"system\",\"content\":pro_sys},{\"role\":\"user\",\"content\":input}]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'role': 'system',\n",
       "  'content': \"You are an expert extraction algorithm.Only extract relevant information from the text.If you do not know the value of an attribute asked to extract,return null for the attribute's value.\"},\n",
       " {'role': 'user', 'content': 'Alan Smith is 6 feet tall and has blond hair.'}]"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p_y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "p_y=prompt_ty(\"Alan Smith is 6 feet tall and has blond hair.\")\n",
    "res=get_response_1t(p_y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'function': {'name': 'Person', 'arguments': '{\"properties\": {\"hair_color\": \"blond\", \"height_in_meters\": \"1.83\", \"name\": \"Alan Smith\"}}'}, 'id': '', 'type': 'function'}\n"
     ]
    }
   ],
   "source": [
    "print(res.output.choices[0].message[\"tool_calls\"][0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 多个对象的抽取和样本的使用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import List, Optional\n",
    "\n",
    "class Person(BaseModel):\n",
    "    \"\"\"Information about a person.\"\"\"\n",
    "    name: Optional[str] = Field(default=None, description=\"The name of the person\")\n",
    "    hair_color: Optional[str] = Field(default=None, description=\"The color of the peron's hair if known\")\n",
    "    height_in_meters: Optional[str] = Field(default=None, description=\"Height measured in meters\")\n",
    "\n",
    "class Data(BaseModel):\n",
    "    \"\"\"Extracted data about people.\"\"\"\n",
    "    people: List[Person]\n",
    "\n",
    "def get_response_1t(mess):\n",
    "    response = Generation.call(\n",
    "        model='qwen-plus',\n",
    "        messages=mess,\n",
    "        tools=[convert_to_openai_tool(Data)],\n",
    "        result_format='message', # 将输出设置为message形式\n",
    "    )\n",
    "    return response\n",
    "\n",
    "def prompt_ty(input):\n",
    "    pro_sys=\"You are an expert extraction algorithm.Only extract relevant information from the text.If you do not know the value of an attribute asked to extract,return null for the attribute's value.\"\n",
    "    return [{\"role\":\"system\",\"content\":pro_sys},{\"role\":\"user\",\"content\":\"Alan Smith is 6 feet tall and has blond hair.\"},\\\n",
    "            {'role': 'assistant', 'content':\"\",'tool_calls': [{'function': {'name': 'Data', 'arguments': '{\"people\": [{\"name\": \"Alan Smith\", \"hair_color\": \"Blond\", \"height_in_meters\": \"1.83\"}]}'}, 'id': '', 'type': 'function'}]},{\"role\":\"user\",\"content\":input}]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "p_y=prompt_ty(\"Alan Smith is 6 feet tall and has blond hair.\")\n",
    "res=get_response_1t(p_y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "GenerationResponse(status_code=<HTTPStatus.OK: 200>, request_id='c1ca95cd-d417-9906-8dae-7bbc858bf2d9', code='', message='', output=GenerationOutput(text=None, choices=[Choice(finish_reason='tool_calls', message=Message({'role': 'assistant', 'content': \"Based on the information provided:\\n\\n- Name: Alan Smith\\n- Hair Color: Blond\\n- Height in Meters: To convert feet to meters, we can use the conversion factor of 1 foot = 0.3048 meters. Let's calculate Alan's height.\", 'tool_calls': [{'function': {'name': 'Data', 'arguments': '{\"people\": [{\"name\": \"Alan Smith\", \"hair_color\": \"Blond\", \"height_in_meters\": \"6 feet\"}]}'}, 'id': '', 'type': 'function'}]}))], finish_reason=None), usage=GenerationUsage(input_tokens=300, output_tokens=96))"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res_ex"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "res_ex=res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'function': {'name': 'Data',\n",
       "   'arguments': '{\"people\": [{\"name\": \"Alan Smith\", \"hair_color\": \"Blond\", \"height_in_meters\": \"6 feet\"}]}'},\n",
       "  'id': '',\n",
       "  'type': 'function'}]"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res_ex.output.choices[0].message[\"tool_calls\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [],
   "source": [
    "text = \"My name is Jeff, my hair is black and i am 6 feet tall. Anna has the same color hair as me.\"\n",
    "p_y=prompt_ty(text)\n",
    "res=get_response_1t(p_y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "GenerationResponse(status_code=<HTTPStatus.OK: 200>, request_id='1424993c-9270-9021-934f-fe6a2cda5736', code='', message='', output=GenerationOutput(text=None, choices=[Choice(finish_reason='tool_calls', message=Message({'role': 'assistant', 'content': '', 'tool_calls': [{'function': {'name': 'Data', 'arguments': '{\"people\": [{\"name\": \"Jeff\", \"hair_color\": \"Black\", \"height_in_meters\": \"1.83\"}, {\"name\": \"Anna\", \"hair_color\": \"Black\", \"height_in_meters\": null}]}'}, 'id': '', 'type': 'function'}]}))], finish_reason=None), usage=GenerationUsage(input_tokens=377, output_tokens=60))"
      ]
     },
     "execution_count": 64,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'function': {'name': 'Data', 'arguments': '{\"people\": [{\"name\": \"Jeff\", \"hair_color\": \"Black\", \"height_in_meters\": \"1.83\"}, {\"name\": \"Anna\", \"hair_color\": \"Black\", \"height_in_meters\": null}]}'}, 'id': '', 'type': 'function'}\n"
     ]
    }
   ],
   "source": [
    "print(res.output.choices[0].message[\"tool_calls\"][0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'type': 'function',\n",
       " 'function': {'name': 'Data',\n",
       "  'description': 'Extracted data about people.',\n",
       "  'parameters': {'type': 'object',\n",
       "   'properties': {'people': {'type': 'array',\n",
       "     'items': {'description': 'Information about a person.',\n",
       "      'type': 'object',\n",
       "      'properties': {'name': {'description': 'The name of the person',\n",
       "        'type': 'string'},\n",
       "       'hair_color': {'description': \"The color of the peron's hair if known\",\n",
       "        'type': 'string'},\n",
       "       'height_in_meters': {'description': 'Height measured in meters',\n",
       "        'type': 'string'}}}}},\n",
       "   'required': ['people']}}}"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "convert_to_openai_tool(Data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "text = \"My name is Jeff, my hair is black and i am 6 feet tall. Anna has the same color hair as me.\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "text = \"The solar system is large, but earth has only 1 moon.\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Message({'role': 'assistant', 'content': \"The text you provided does not contain information about the number of people or their specific details. It discusses the size of the solar system and the fact that Earth has only one moon. Here's the relevant information:\\n\\n- Solar System Size: Large (not quantified)\\n- Earth's Moons: 1 (specifically, the Moon)\\n\\nIf you need a count of people or details about individuals, please provide another text.\"})"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res.output.choices[0].message"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 基于大模型抽取人物关系图谱\n",
    "\n",
    "* 文档的分割\n",
    "* 案例的重要性"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.document_loaders import TextLoader\n",
    "loader_txt = TextLoader(r'F:\\langchain\\云岚宗.txt', encoding='utf8')\n",
    "docs_txt = loader_txt.load()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
    "# 导入递归字符文本分割器\n",
    "text_splitter_txt = RecursiveCharacterTextSplitter(chunk_size = 300, chunk_overlap = 0, separators=[\"\\n\\n\", \"\\n\", \" \", \"\", \"。\", \"，\"])\n",
    "# 导入文本\n",
    "documents_txt = text_splitter_txt.split_documents(docs_txt)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "9"
      ]
     },
     "execution_count": 71,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(documents_txt)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'第四章 云岚宗\\n\\n大厅中,萧战以及三位长老,正在颇为热切的与那位陌生老者交谈着,不过这位老者似乎有什么难以启齿的事情一般,每每到口的话语,都将会有些无奈的咽了回去,而每当这个时候,一旁的娇贵少女,都是忍不住的横了老者一眼…\\n\\n    倾耳听了一会，萧炎便是有些无聊的摇了摇头…\\n\\n    “萧炎哥哥，你知道他们的身份吗？”就在萧炎无聊得想要打瞌睡之时，身旁的熏儿，纤指再次翻开古朴的书页，目不斜视的微笑道。\\n\\n    “你知道？”好奇的转过头来，萧炎惊诧的问道。\\n\\n    “看见他们袍服袖口处的云彩银剑了么？”微微一笑，熏儿道。'"
      ]
     },
     "execution_count": 72,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "documents_txt[0].page_content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Relation(BaseModel):\n",
    "    \"\"\"两个人物之间的关系\"\"\"\n",
    "    name0: Optional[str] = Field(default=None, description=\"一个人物的名称\")\n",
    "    name1: Optional[str] = Field(default=None, description=\"另一个人物的名称\")\n",
    "    guanxi: Optional[str] = Field(default=None, description=\"两个人物间的关系\")\n",
    "\n",
    "class Data_guanxi(BaseModel):\n",
    "    \"\"\"抽取人物间关系数据\"\"\"\n",
    "    relation: List[Relation]\n",
    "\n",
    "def prompt_yun(ct):\n",
    "    system_prompt=\"您是提取算法专家。仅从文本中提取相关信息。如果您不知道要求提取的属性值，则为属性值返回 null。首先确定文本中包含哪些人物角色，给出这些人物角色间的关系\"\n",
    "    prompt=[{\"content\":system_prompt,\"role\":\"system\"},\\\n",
    "            {\"role\":\"user\",\"content\":\"小明上学路上看手机，被小强的足球踢脸上了，小红看见了哈哈大笑\"},\\\n",
    "            {'role': 'assistant', 'content': '', 'tool_calls': [{'function': {'name': 'Data_yun', 'arguments': '{\"relation\": [{\"name0\": \"小明\",\"name1\": \"小强\",\"guanxi\": \"小明被小强把球踢脸上了\"},{\"name0\": \"小红\",\"name1\": \"小明\",\"guanxi\": \"小明被小红嘲笑了\"}]}'}}]}]\n",
    "    prompt.append({\"role\":\"user\",\"content\":ct})\n",
    "    return prompt\n",
    "\n",
    "def get_response_1t(mess):\n",
    "    response = Generation.call(\n",
    "        model='qwen-plus',\n",
    "        messages=mess,\n",
    "        tools=[convert_to_openai_tool(Data_guanxi)],\n",
    "        result_format='message', # 将输出设置为message形式\n",
    "    )\n",
    "    return response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'第四章 云岚宗\\n\\n大厅中,萧战以及三位长老,正在颇为热切的与那位陌生老者交谈着,不过这位老者似乎有什么难以启齿的事情一般,每每到口的话语,都将会有些无奈的咽了回去,而每当这个时候,一旁的娇贵少女,都是忍不住的横了老者一眼…\\n\\n    倾耳听了一会，萧炎便是有些无聊的摇了摇头…\\n\\n    “萧炎哥哥，你知道他们的身份吗？”就在萧炎无聊得想要打瞌睡之时，身旁的熏儿，纤指再次翻开古朴的书页，目不斜视的微笑道。\\n\\n    “你知道？”好奇的转过头来，萧炎惊诧的问道。\\n\\n    “看见他们袍服袖口处的云彩银剑了么？”微微一笑，熏儿道。'"
      ]
     },
     "execution_count": 79,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "documents_txt[0].page_content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "metadata": {},
   "outputs": [],
   "source": [
    "text = documents_txt[0].page_content\n",
    "p_y=prompt_yun(text)\n",
    "res=get_response_1t(p_y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'name0': '萧战', 'name1': '三位长老', 'guanxi': '与...交谈'},\n",
       " {'name0': '陌生老者', 'name1': '萧战', 'guanxi': '与...交谈'},\n",
       " {'name0': '娇贵少女', 'name1': '老者', 'guanxi': '横了...一眼'},\n",
       " {'name0': '萧炎', 'name1': '熏儿', 'guanxi': '对话'}]"
      ]
     },
     "execution_count": 91,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res_relation=parser.parse(res.output.choices[0].message['tool_calls'][0]['function']['arguments'])['relation']\n",
    "res_relation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'result': '2 + 2 = 4', 'mode': 'math_operation'}"
      ]
     },
     "execution_count": 88,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_core.output_parsers import JsonOutputParser\n",
    "\n",
    "parser = JsonOutputParser()\n",
    "output_dict = parser.parse('{\"result\": \"2 + 2 = 4\", \"mode\": \"math_operation\"}')\n",
    "output_dict"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [],
   "source": [
    "text = documents_txt[0].page_content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Message({'role': 'assistant', 'content': '', 'tool_calls': [{'function': {'name': 'Data_yun', 'arguments': '{\"relation\": [{\"name0\": \"萧炎\", \"name1\": \"萧战\", \"guanxi\": \"正在交谈\"}, {\"name0\": \"萧炎\", \"name1\": \"三位长老\", \"guanxi\": \"正在交谈\"}, {\"name0\": \"一位陌生老者\", \"name1\": \"萧炎\", \"guanxi\": \"交谈时老者有难言之隐\"}, {\"name0\": \"娇贵少女\", \"name1\": \"老者\", \"guanxi\": \"少女对老者不满（可能是因为老者的犹豫）\"}, {\"name0\": \"熏儿\", \"name1\": \"萧炎\", \"guanxi\": \"向萧炎询问身份\"}]}'}, 'id': '', 'type': 'function'}]})"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res.output.choices[0].message"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\"relation\": [{\"name0\": \"萧炎\", \"name1\": \"萧战\", \"guanxi\": \"正在交谈\"}, {\"name0\": \"萧炎\", \"name1\": \"三位长老\", \"guanxi\": \"正在交谈\"}, {\"name0\": \"一位陌生老者\", \"name1\": \"萧炎\", \"guanxi\": \"交谈时老者有难言之隐\"}, {\"name0\": \"娇贵少女\", \"name1\": \"老者\", \"guanxi\": \"少女对老者不满（可能是因为老者的犹豫）\"}, {\"name0\": \"熏儿\", \"name1\": \"萧炎\", \"guanxi\": \"向萧炎询问身份\"}]}\n"
     ]
    }
   ],
   "source": [
    "print(res.output.choices[0].message[\"tool_calls\"][0]['function']['arguments'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 93,
   "metadata": {},
   "outputs": [],
   "source": [
    "#正常显示画图时出现的中文和负号\n",
    "from pylab import mpl\n",
    "import networkx as nx\n",
    "import matplotlib.pyplot as plt\n",
    "import random\n",
    "\n",
    "mpl.rcParams['font.sans-serif']=['SimHei']\n",
    "mpl.rcParams['axes.unicode_minus']=False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'name0': '萧炎', 'name1': '萧战', 'guanxi': '正在交谈'},\n",
       " {'name0': '萧炎', 'name1': '三位长老', 'guanxi': '正在交谈'},\n",
       " {'name0': '一位陌生老者', 'name1': '萧炎', 'guanxi': '交谈时老者有难言之隐'},\n",
       " {'name0': '娇贵少女', 'name1': '老者', 'guanxi': '少女对老者不满（可能是因为老者的犹豫）'},\n",
       " {'name0': '熏儿', 'name1': '萧炎', 'guanxi': '向萧炎询问身份'}]"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res_relation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "G = nx.Graph()\n",
    "for i in res_relation:\n",
    "    G.add_edge(i[\"name0\"], i['name1'], name=i['guanxi'])\n",
    "pos = nx.spring_layout(G) \n",
    "\n",
    "for i in pos.keys():\n",
    "    pos[i][0]=pos[i][0]+random.randint(0, 20)\n",
    "    pos[i][1]=pos[i][1]+random.randint(0, 20)\n",
    "    \n",
    "nx.draw_networkx(G, pos=pos)\n",
    "edge_labels = nx.get_edge_attributes(G, 'name')\n",
    "nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 基于大模型的商品评论分析"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [],
   "source": [
    "df=pd.read_excel(r\"F:\\langchain\\原始数据.xlsx\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Index(['Class_Name', 'Clothing_ID', 'Department_Name', 'Division_Name',\n",
       "       'Review_Text', 'Title', 'Age', 'F0', 'Positive_Feedback_Count',\n",
       "       'Rating', 'Recommended_IND'],\n",
       "      dtype='object')"
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df.columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 135,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Class_Name</th>\n",
       "      <th>Clothing_ID</th>\n",
       "      <th>Department_Name</th>\n",
       "      <th>Division_Name</th>\n",
       "      <th>Review_Text</th>\n",
       "      <th>Title</th>\n",
       "      <th>Age</th>\n",
       "      <th>F0</th>\n",
       "      <th>Positive_Feedback_Count</th>\n",
       "      <th>Rating</th>\n",
       "      <th>Recommended_IND</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>Intimates</td>\n",
       "      <td>767</td>\n",
       "      <td>Intimate</td>\n",
       "      <td>Initmates</td>\n",
       "      <td>Absolutely wonderful - silky and sexy and comf...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>33</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>4</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>Dresses</td>\n",
       "      <td>1080</td>\n",
       "      <td>Dresses</td>\n",
       "      <td>General</td>\n",
       "      <td>Love this dress!  it's sooo pretty.  i happene...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>34</td>\n",
       "      <td>1</td>\n",
       "      <td>4</td>\n",
       "      <td>5</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "  Class_Name  Clothing_ID Department_Name Division_Name  \\\n",
       "0  Intimates          767        Intimate     Initmates   \n",
       "1    Dresses         1080         Dresses       General   \n",
       "\n",
       "                                         Review_Text Title  Age  F0  \\\n",
       "0  Absolutely wonderful - silky and sexy and comf...   NaN   33   0   \n",
       "1  Love this dress!  it's sooo pretty.  i happene...   NaN   34   1   \n",
       "\n",
       "   Positive_Feedback_Count  Rating  Recommended_IND  \n",
       "0                        0       4                1  \n",
       "1                        4       5                1  "
      ]
     },
     "execution_count": 135,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df.head(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 144,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Clothing_ID\n",
       "1056    11\n",
       "1072    13\n",
       "820     13\n",
       "829     14\n",
       "867     16\n",
       "1081    16\n",
       "868     19\n",
       "1094    24\n",
       "1078    29\n",
       "862     34\n",
       "dtype: int64"
      ]
     },
     "execution_count": 144,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df[df[\"Rating\"]<2].groupby(\"Clothing_ID\").size().sort_values().tail(10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [],
   "source": [
    "re_862=df[(df[\"Rating\"]<2)&(df[\"Clothing_ID\"]==862)].Review_Text.tolist()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 154,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'The material is cheap and looks torn. not as nice as the picture.'"
      ]
     },
     "execution_count": 154,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "re_862[1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [],
   "source": [
    "class clothing(BaseModel):\n",
    "    \"\"\"Reasons for negative user reviews\"\"\"\n",
    "    Fabric_problems: Optional[str] = Field(default=None, description=\"Fabric problems: noticeable holes, loose threads, pilling, or abnormal color fading and shrinkage\")\n",
    "    Workmanship_defects: Optional[str] = Field(default=None, description=\"Workmanship defects: poor stitching, excessive loose threads, easily detachable buttons, etc\")\n",
    "    Size_discrepancies : Optional[str] = Field(default=None, description=\"Size discrepancies: the actual product size differs from the described dimensions, leading to ill-fitting garments.\")\n",
    "    Uncomfortable_wear : Optional[str] = Field(default=None, description=\"non-breathable fabrics, irritation against the skin, or discomfort during wear.\")\n",
    "    Fit_design: Optional[str] = Field(default=None, description=\"Fit and design: unsuitable cut, not conforming to ergonomics, or unflattering when worn\")\n",
    "    Appearance_Discrepancies: Optional[str] = Field(default=None, description=\"Mismatch between the product received and online images in terms of style or color.\")\n",
    "    After_Sales: Optional[str] = Field(default=None, description=\"Unsatisfactory customer service attitude or response efficiency.\")\n",
    "    Value_Money: Optional[str] = Field(default=None, description=\"Consumers feel that the price does not align with the actual quality or value of the product, perceiving it as overpriced.\")\n",
    "\n",
    "class Data_yun(BaseModel):\n",
    "    \"\"\"Extraction of user negative review information\"\"\"\n",
    "    comment: List[clothing]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "#convert_to_openai_tool(Data_yun)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {},
   "outputs": [],
   "source": [
    "text = re_862[4]\n",
    "p_y=prompt_yun(text)\n",
    "res=get_response_yun(p_y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 188,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\"comment\": [{\"Size_discrepancies\": \"shrank significantly after washing, became unwearable\", \"Workmanship_defects\": \"poor shrink resistance\"}]}\n"
     ]
    }
   ],
   "source": [
    "print(res.output.choices[0].message[\"tool_calls\"][0]['function']['arguments'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 180,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'I loved this shirt until the first time i washed it. it shrunk so much it became unwearable. when i returned it the salesperson said she had also bought this shirt and the same thing happened.'"
      ]
     },
     "execution_count": 180,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "re_862[4]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 189,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'{\"comment\": [{\"Size_discrepancies\": \"shrank significantly after washing, became unwearable\", \"Workmanship_defects\": \"poor shrink resistance\"}]}'"
      ]
     },
     "execution_count": 189,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res.output.choices[0].message[\"tool_calls\"][0]['function']['arguments']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 211,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'Fit_design': 25,\n",
       " 'Fabric_problems': 5,\n",
       " 'Appearance_Discrepancies': 6,\n",
       " 'Size_discrepancies': 12,\n",
       " 'Return_policy': 1,\n",
       " 'Value_Money': 9,\n",
       " 'Uncomfortable_wear': 4,\n",
       " 'Workmanship_defects': 9,\n",
       " 'Unsuitable_product': 1,\n",
       " 'Quality': 1}"
      ]
     },
     "execution_count": 211,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dict0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "https://api.python.langchain.com/en/latest/_modules/langchain_anthropic/chat_models.html#ChatAnthropic.bind_tools\n",
    "\n",
    "https://zhuanlan.zhihu.com/p/685741041\n",
    "\n",
    "https://github.com/langchain-ai/langchain/blob/a4896da2a0264c3405b5a97d03fff673ddee8402/libs/partners/openai/langchain_openai/chat_models/base.py#L765"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
